package stardust.core.network.protocol.proxy.http;


import java.net.MalformedURLException;
import java.net.SocketTimeoutException;
import java.net.URL;
import stardust.core.network.NetworkException;
import stardust.core.network.NetworkSession;
import stardust.core.network.NetworkSessionInputStream;
import stardust.core.network.NetworkSessionOutputStream;
import stardust.core.network.NetworkState;
import stardust.core.network.ProtocolType;
import stardust.core.network.protocol.http.ConnectionType;
import stardust.core.network.protocol.http.HttpMethod;
import stardust.core.network.protocol.http.HttpRequestMeta;
import stardust.core.network.protocol.http.HttpResponseMeta;


public class CopyOfHttpProxyProcessor extends Thread
{

  private NetworkSession session;

  public CopyOfHttpProxyProcessor(NetworkSession session)
  {
    this.session = session;
  }

  public void run()
  {
    NetworkSession connect = null;

    try
    {
      // 客户端代理请求。
      NetworkSessionInputStream requestInput = new NetworkSessionInputStream(this.session);
      NetworkSessionOutputStream requestOutput = new NetworkSessionOutputStream(this.session);
      HttpRequestMeta requestMeta;

      // 目标服务器。
      NetworkSessionInputStream connectInput = new NetworkSessionInputStream(connect);
      NetworkSessionOutputStream connectOutput = new NetworkSessionOutputStream(connect);
      HttpResponseMeta connectMeta;

      // 目标地址。
      String url;

      // 在 Connection 或者 Proxy-Connection 为 KeepAlive 时跳转到此处继续处理下一个请求。
      do
      {
        requestMeta = HttpRequestMeta.parse(requestInput);
        Long requestLength = requestMeta.getOthers().containsKey("Content-Length") ? Long.valueOf(requestMeta.getOthers().get("Content-Length")) : null;

        // 客户端想要连接的 URL。
        url = this.processRequest(requestMeta);

        // 发起目标服务器连接的请求。
        if (connect != null && connect.getState() == NetworkState.closed)
        {
          connect.disconnect();
          connect = null;
        }
        if (connect == null)
        {
          if (requestMeta.getMethod() == HttpMethod.connect)
          {
            // todo 目前不支持 HTTPS隧道,但是支持HTTP隧道
            // connect = NetworkSession.getInstance(ProtocolType.secureTcp);
            connect = NetworkSession.getInstance(ProtocolType.tcp);
          }
          else
          {
            // todo 目前不支持 HTTPS代理,但是支持HTTP代理
            // connect = NetworkSession.getInstance(ProtocolType.secureTcp);
            connect = NetworkSession.getInstance(ProtocolType.tcp);
          }

          URL u = new URL(url);

          // connect.connect("10.210.1.49", 8080);
          connect.connect(u.getHost(), u.getPort() == -1 ? 80 : u.getPort());

        }
        connectInput = new NetworkSessionInputStream(connect);
        connectOutput = new NetworkSessionOutputStream(connect);

        if (requestMeta.getMethod() == HttpMethod.connect)
        {
          /*
           * CONNECT 方式的 HTTP 隧道连接 。
           */
          this.session.getConfiguration().setTimeout(10000);
          this.session.apply();
          connect.getConfiguration().setTimeout(10000);
          connect.apply();

          this.printSuccess();

          byte[] buffer = new byte[1024];
          int readLength;

          while (true)
          {
            try
            {
              // 读入客户机数据并写入服务器。
              readLength = requestInput.read(buffer);

              if (readLength == -1)
              {
                ;
              }
              else
              {
                connect.write(buffer, 0, readLength);
              }
            }
            catch(NetworkException e)
            {
              if (((Object) e.getCause()) instanceof SocketTimeoutException)
              {
                ;
              }
              else
              {
                throw e;
              }
            }

            try
            {
              // 读入服务器数据并写入客户机 。
              readLength = connectInput.read(buffer);

              if (readLength == -1)
              {
                ;
              }
              else
              {
                requestOutput.write(buffer, 0, readLength);
              }
            }
            catch(NetworkException e)
            {
              if (((Object) e.getCause()) instanceof SocketTimeoutException)
              {
                ;
              }
              else
              {
                throw e;
              }
            }
          }
        }
        else
        {
          /*
           * 发送服务器请求。
           */
          System.out.println(requestMeta.getRequest() + "\n\n");
          // 写入请求头。
          String proxyConnection = requestMeta.getOthers().remove("Proxy-Connection");
          connectOutput.write(requestMeta.getRequest());
          requestMeta.getOthers().put("Proxy-Connection", proxyConnection);
          // 如果请求存在 Content-Length 头就写入请求实体。
          if (requestLength != null || requestMeta.getMethod() == HttpMethod.post)
          {
            byte[] buffer = new byte[102400];
            int readLength = 0;
            int writeLength = 0;
            while (writeLength < requestLength)
            {
              readLength = requestInput.read(buffer);
              connectOutput.write(buffer, 0, readLength);
              writeLength += readLength;
              System.out.println(requestMeta.getRequestURI() + "\t" + new String(buffer, 0, readLength));
            }
          }

          /*
           * 返回服务器响应。
           */
          connectMeta = HttpResponseMeta.parse(connectInput);

          // 服务器的响应长度，当没有获取到这个值时将读取所有数据直到服务器主动关闭连接。
          Long responseLength = connectMeta.getOthers().get("Content-Length") == null ? null : Long.parseLong(connectMeta.getOthers().get("Content-Length"));
          Long writeLength = 0L;

          // 向代理请求端返回数据。
          byte[] buffer = new byte[1024];
          int readLength;

          requestOutput.write(connectMeta.getResponse());

          while ((responseLength != null && writeLength < responseLength) || responseLength == null)
          {
            readLength = connectInput.read(buffer);

            if (readLength == -1)
            {
              break;
            }

            // 写入数据到请求端
            try
            {
              requestOutput.write(buffer, 0, readLength);
            }
            catch(Exception e)
            {
              System.gc();
            }
            writeLength += readLength;

            // if (responseLength == null && readLength < buffer.length)
            // {
            // // 对没有提供 Content-Length 头的服务器提供容错机制，比如 g.cn 用 gzip
            // 压缩数据，同时客户机请求保持连接，无法获取数据长度无法得知何时是当前请求结束 keepAlive。
            // keepAlive = false;
            // break;
            // }
          }
        }
      }
      while (false && ConnectionType.get(requestMeta.getOthers().get("Proxy-Connection")) == ConnectionType.keepAlive
             && ConnectionType.get(connectMeta.getOthers().get("Connection")) == ConnectionType.keepAlive);
    }
    catch(Throwable e)
    {
      // System.err.println(e);
      e.printStackTrace();
      this.printError(e);
    }
    finally
    {
      if (connect != null)
      {
        connect.disconnect();
      }

      this.session.disconnect();
    }
  }

  private String processRequest(HttpRequestMeta requestMeta) throws MalformedURLException
  {
    String strUrl;
    URL url;

    if (requestMeta.getMethod() == HttpMethod.connect)
    {
      // requestMeta.setRequestURI("http://" + requestMeta.getRequestURI());
    }

    if (!requestMeta.getRequestURI().toLowerCase().startsWith("http://"))
    {
      url = new URL("http://" + requestMeta.getRequestURI());
      strUrl = "http://" + requestMeta.getRequestURI();
    }
    else
    {
      url = new URL(requestMeta.getRequestURI());
      strUrl = requestMeta.getRequestURI();
    }

    if ("*/*".equals(requestMeta.getOthers().get("Accept")))
    {
      requestMeta.getOthers().put(
      "Accept",
      "image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/x-shockwave-flash, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */*");
    }

    if (requestMeta.getOthers().containsKey("Host"))
    {
      String[] host = requestMeta.getOthers().get("Host").split(":", 2);

      url = new URL("http://" + host[0] + ":" + (host.length > 1 ? host[1] : 80) + url.getPath());
    }
    else
    {
      requestMeta.getOthers().put("Host", url.getHost() + ":" + url.getPort());
    }

    return strUrl;
  }

  private void printError(Throwable e)
  {
    byte[] error =
           ("HTTP/1.0 503 Error\n"
            + "Server: Stardust Proxy Server(http)\n"
            + "Content-Type: text/html; charset=UTF-8\n"
            + "\n"
            + "<html><body>"
            + "<h1><b>HTTP 503</b></h1><p />Can't connect to server,please check your network configuration or URL.<hr /><p />Exception:<br />"
            + e.getMessage()
            + "<p /><hr /><b>Kitson Proxy</b>"
            + "<br /><b><a href=\"mailto:1072027237@qq.com\">Send mail to stardust</a></b>"
            + "</body></html>"
            + "\n\n").getBytes();

    try
    {
      this.session.write(error, 0, error.length);
    }
    catch(Throwable ex)
    {
      this.session.disconnect();
    }
  }

  private void printSuccess()
  {
    byte[] message =
           ("HTTP/1.1 200 OK\n"
            + "Server: Stardust Proxy Server(http)\n"
            + "\n").getBytes();

    try
    {
      this.session.write(message, 0, message.length);
    }
    catch(Throwable ex)
    {
      this.session.disconnect();
    }
  }

}
